# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import collections
import logging

PerProductTradeCheckerStatus = collections.namedtuple('PerProductBookCheckerStatus',
                                                      [
                                                          'trade_valid',
                                                          'trade_count',
                                                          'last_trade_received_sec',
                                                          'last_trade_changed_sec',
                                                      ])


class PerProductTradeChecker(object):
  def __init__(self, *, product, config, timestamp, logger=None):
    self._product = product
    self._config = config
    self._initialized_at = timestamp

    self._last_received_at = None
    self._last_changed_at = None
    self._last_valid_at = None

    self._last_trade_event = None

    # TODO(jshin): Parameterize
    self._valid_book_depth = 3
    self._max_spread_ratio = 0.05

    self._count = 0
    self._valid = True
    self._logger = logger or logging.getLogger(__name__)

  @property
  def valid(self):
    return self._valid

  @property
  def count(self):
    return self._count

  def _check_timestamp(self, *, timestamp):
    # No feed
    if self._last_received_at is None:
      last_received_before = timestamp - self._initialized_at
    else:
      last_received_before = timestamp - self._last_received_at
    if self._config.max_trade_gap < last_received_before < 1e14:
      self._logger.warning('%s: No feed in %.2f seconds', self._product, last_received_before / 1e9)
      return False
    # No change
    if self._last_changed_at is None:
      last_changed_before = timestamp - self._initialized_at
    else:
      last_changed_before = timestamp - self._last_changed_at
    if self._config.max_trade_change_gap < last_changed_before < 1e14:
      self._logger.warning('%s: No book change in %s seconds',
                           self._product,
                           last_changed_before / 1e9)
      return False
    return True

  def _check_validity(self, *, trade_event):
    if trade_event.qty <= 0:
      self._logger.warning('%s: Negative qty (%s)', self._product, trade_event)
      return False
    if trade_event.price <= 0:
      self._logger.warning('%s: Negative price (%s)', self._product, trade_event)
      return False
    return True

  def update_timestamp(self, *, timestamp):
    if not self._valid:
      return
    if not self._check_timestamp(timestamp=timestamp):
      self._valid = False
      return
    self._last_valid_at = timestamp

  def on_trade(self, *, trade_event, timestamp):
    if not self._valid:
      return

    if not self._check_timestamp(timestamp=timestamp):
      self._valid = False
      return

    if not self._check_validity(trade_event=trade_event):
      self._valid = False
      return

    self._last_received_at = timestamp
    if self._last_trade_event is None or any((self._last_trade_event.price != trade_event.price,
                                              self._last_trade_event.qty != trade_event.qty,
                                              self._last_trade_event.side != trade_event.side)):
      self._last_changed_at = timestamp
    self._last_trade_event = trade_event
    self._count += 1

  def get_status(self, *, timestamp):
    return PerProductTradeCheckerStatus(
        trade_valid=self._valid,
        trade_count=self._count,
        last_trade_received_sec=(timestamp - self._last_received_at)
        * 1e-9 if self._last_received_at else None,
        last_trade_changed_sec=(timestamp - self._last_changed_at)
        * 1e-9 if self._last_changed_at else None,
    )
